﻿#region Using
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endregion Using

namespace LloydGame
{
    public class PointLightShadowMap : ShadowMap
    {
        protected RenderTargetCube m_renderTarget;

        // constructor
        public PointLightShadowMap(Game p_game, int p_shadowMapSize) : base(p_game, p_shadowMapSize)
        {
            m_renderTarget = new RenderTargetCube(
                p_game.GraphicsDevice,
                m_shadowMapSize,
                1,
                SurfaceFormat.Color);

            m_shadowStencilBuffer = new DepthStencilBuffer(
                p_game.GraphicsDevice,
                m_shadowMapSize,
                m_shadowMapSize,
                p_game.GraphicsDevice.DepthStencilBuffer.Format);
        }

        #region Shadow Map Creation
        public override void createShadowMap(GameTime p_gameTime, GameLight p_light, bool p_createStatic)
        {
            // temporarily dissable blending
            bool alphaBlendEnableBackup = Game.GraphicsDevice.RenderState.AlphaBlendEnable;
            Game.GraphicsDevice.RenderState.AlphaBlendEnable = false;

            // temporarily enable writing to the depth buffer
            bool depthBufferWriteEnableBackup = Game.GraphicsDevice.RenderState.DepthBufferWriteEnable;
            Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;

            // temporarily replace the stencil buffer
            DepthStencilBuffer stencilBufferBackup = Game.GraphicsDevice.DepthStencilBuffer;
            Game.GraphicsDevice.DepthStencilBuffer = m_shadowStencilBuffer;

            // setup shared parameters
            m_depthMapEffect.Parameters["lightFarClip"].SetValue(p_light.falloffEnd);
            m_depthMapEffect.Parameters["lightPosition"].SetValue(p_light.position);

            if (p_createStatic)
            {
                // draw a depth map into all the cube maps faces
                drawDepthMapCubeFace(p_gameTime, CubeMapFace.NegativeX, p_light, true);
                drawDepthMapCubeFace(p_gameTime, CubeMapFace.NegativeY, p_light, true);
                drawDepthMapCubeFace(p_gameTime, CubeMapFace.NegativeZ, p_light, true);
                drawDepthMapCubeFace(p_gameTime, CubeMapFace.PositiveX, p_light, true);
                drawDepthMapCubeFace(p_gameTime, CubeMapFace.PositiveY, p_light, true);
                drawDepthMapCubeFace(p_gameTime, CubeMapFace.PositiveZ, p_light, true);

                // set render target back to the screen
                Game.GraphicsDevice.SetRenderTarget(0, null);

                // save the static depth map
                m_staticShadowMap = m_renderTarget.GetTexture();
            }

            // draw a depth map into all the cube maps faces
            drawDepthMapCubeFace(p_gameTime, CubeMapFace.NegativeX, p_light, false);
            drawDepthMapCubeFace(p_gameTime, CubeMapFace.NegativeY, p_light, false);
            drawDepthMapCubeFace(p_gameTime, CubeMapFace.NegativeZ, p_light, false);
            drawDepthMapCubeFace(p_gameTime, CubeMapFace.PositiveX, p_light, false);
            drawDepthMapCubeFace(p_gameTime, CubeMapFace.PositiveY, p_light, false);
            drawDepthMapCubeFace(p_gameTime, CubeMapFace.PositiveZ, p_light, false);

            // set render target back to the screen
            Game.GraphicsDevice.SetRenderTarget(0, null);

            // save the depth map
            m_shadowMap = m_renderTarget.GetTexture();

            // restore the graphics device settings
            Game.GraphicsDevice.DepthStencilBuffer = stencilBufferBackup;
            Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = depthBufferWriteEnableBackup;
            Game.GraphicsDevice.RenderState.AlphaBlendEnable = alphaBlendEnableBackup;
        }

        private void drawDepthMapCubeFace(GameTime p_gameTime, CubeMapFace p_cubeMapFace
            , GameLight p_light, bool p_createStatic)
        {
            Vector3 viewTarget = p_light.position;
            Vector3 upVector = Vector3.Up;

            switch (p_cubeMapFace)
            {
                case CubeMapFace.NegativeX:
                    viewTarget += new Vector3(-1, 0, 0);
                    break;
                case CubeMapFace.NegativeY:
                    viewTarget += new Vector3(0, -1, 0);
                    upVector = new Vector3(0, 0, -1);
                    break;
                case CubeMapFace.NegativeZ:
                    viewTarget += new Vector3(0, 0, 1);
                    break;
                case CubeMapFace.PositiveX:
                    viewTarget += new Vector3(1, 0, 0);
                    break;
                case CubeMapFace.PositiveY:
                    viewTarget += new Vector3(0, 1, 0);
                    break;
                case CubeMapFace.PositiveZ:
                    viewTarget += new Vector3(0, 0, -1);
                    break;
            }

            // set the light's render target
            Game.GraphicsDevice.SetRenderTarget(0, m_renderTarget, p_cubeMapFace);

            // let every texel be "in light"
            Game.GraphicsDevice.Clear(Color.White);

            // setup view and projection matrices
            Matrix view = Matrix.CreateLookAt(p_light.position, viewTarget, upVector);
            Matrix proj = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(90.0f), 1.0f, 1.0f, p_light.falloffEnd);

            m_viewProj = view * proj;

            drawDepthMap(p_gameTime, p_createStatic);
        }
        #endregion Shadow Map Creation
    }
}
